package top.market.redis;

import com.alibaba.fastjson.JSON;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.List;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

@Component
public class LyhRedisMethodUtils {

    @Resource
    private  LyhStringRedisTemplate lyhStringRedisTemplate;

    @Resource
    private  RedissonClient redissonClient;

    // 根据逻辑过期时间来设置的,  自己包装一个obj 包含这个属性即可
    public<T> Object getSetToBreakdownByExpireTime(Object keyObj, Object queryObj,  LyhCachetValueAbstractFunction<Object> function , Long time, TimeUnit unit) {
        String key = JSON.toJSONString(keyObj);
        LyhRedisData redisData = lyhStringRedisTemplate.getObjStr(key, LyhRedisData.class);
        Object data = null;
        // 这里是直接没数据， 互斥锁 + 开辟线程缓存
        if (LyhObjUtils.isEmpty(redisData)) {
            newThreadToGetDataAndLock(keyObj, queryObj, function, time, unit);
        }else {
            data = redisData.getObject();
            LocalDateTime expireTime = redisData.getExpireTime();
            // 过期数据格式不规范，直接删除
            if (LyhObjUtils.isEmpty(expireTime) ){
                lyhStringRedisTemplate.delete(key);
                newThreadToGetDataAndLock(keyObj, queryObj,  function, time, unit);
            }
            if (LyhObjUtils.isNotEmpty(expireTime) && expireTime.isBefore(LocalDateTime.now())){
                newThreadToGetDataAndLock(keyObj, queryObj, function, time, unit);
            }
        }
        return data;
    }

    // 异步线程存上1小时
    private void newThreadToGetDataAndLock(Object keyObj,  Object queryObj, LyhCachetValueAbstractFunction<Object> function, Long time, TimeUnit unit){
        String key = JSON.toJSONString(keyObj);
        RLock lock = redissonClient.getLock(key);
        try {
            boolean b = lock.tryLock(2, TimeUnit.SECONDS);
            if (b){
                // 双重判断
                LyhRedisData secondData = lyhStringRedisTemplate.getObjStr(key, LyhRedisData.class);
                if (LyhObjUtils.isNotEmpty(secondData)){
                    LocalDateTime expireTime = secondData.getExpireTime();
                    if (expireTime.isAfter(LocalDateTime.now())) return;
                }
                // 异步执行操作, 可以使用线程池来做（因为频繁创建新的线程十分耗费志愿，并且这个业务并发量较高）
                // 但是线程池也会存在问题，因为这个方法是模板方法， 如果多个服务都来使用，那么如何避免过载？
                Future future = LyhThreadUtils.execAsync(() -> {
                    Object object = function.get(queryObj);
                    LyhRedisData redisData = new LyhRedisData();
                    redisData.setObject(object);
                    long l = time;
                    if (unit == TimeUnit.MINUTES) {
                        l = Math.multiplyExact(time, 60l);
                    }
                    if (unit == TimeUnit.MILLISECONDS){
                        l = l / 1000;
                    }
                    if (unit == TimeUnit.HOURS){
                        l = Math.multiplyExact(l, 60 * 60 * 24l);
                    }
                    redisData.setExpireTime(LocalDateTime.now().plusSeconds(l));
                    lyhStringRedisTemplate.setObjStr(key, redisData, 10l, TimeUnit.SECONDS);
                });
            }
        } catch (InterruptedException e) {
            // LyhLoggerUtils.error(this.getClass(), e.getMessage());
            throw new RuntimeException(e);
        }finally {
            lock.unlock();
        }
    }

    public Object getSetToBreakdownByWaiting(Object keyObj,  Object queryObj, LyhCachetValueAbstractFunction<Object> function ,  Long time, TimeUnit unit) {
        String key = JSON.toJSONString(keyObj);
        Object objStr = lyhStringRedisTemplate.getObjStr(key, Object.class);    // 首次判断
        if (LyhObjUtils.isNotEmpty(objStr)) return objStr;
        RLock lock = redissonClient.getLock(key);
        try {
            boolean b = lock.tryLock(2, TimeUnit.SECONDS);
            if (b){
                objStr = lyhStringRedisTemplate.getObjStr(key, Object.class);   // 双重检查缓存
                if (LyhObjUtils.isNotEmpty(objStr)) return objStr;
                Object obj = function.get(queryObj);
                lyhStringRedisTemplate.setObjStr(key, obj, time, unit);
                return obj;
            }
        } catch (InterruptedException e) {
            // LyhLoggerUtils.error(this.getClass(), e.getMessage());
            throw new RuntimeException(e);
        }finally {
            lock.unlock();
        }
        int epoch = 3; // 总共停顿900ms时间, 循环等待一段时间, 每次停顿300ms
        while (LyhObjUtils.isEmpty(objStr) && epoch > 0) {
            try {
                epoch--;
                TimeUnit.MILLISECONDS.sleep(300l);
                objStr = lyhStringRedisTemplate.getObjStr(key, Object.class);
            } catch (InterruptedException e) {
                // LyhLoggerUtils.error(this.getClass(), e.getMessage());
                throw new RuntimeException(e);
            }
        }
        return objStr;
    }

    public Object getSetObjectByMethod(Object keyObj, Object queryObj, LyhCachetValueAbstractFunction<Object> function ,Long time, TimeUnit unit) {
        String key = JSON.toJSONString(keyObj);
        Object obj = lyhStringRedisTemplate.getObjArrayStr(key, Object.class);
        if (LyhObjUtils.isEmpty(obj)){
            obj =  function.get(queryObj);
            if (LyhObjUtils.isNotEmpty(obj)){
                lyhStringRedisTemplate.setObjStr(key, obj, time, unit);
            }
        }
        return obj;
    }

    public<T> List<T> getSetArrayByMethod(Object keyObj, Object queryObj, LyhCachetValueAbstractFunction<List<T>> function , Class<T> clazz, Long time, TimeUnit unit) {
        String key = JSON.toJSONString(keyObj);
        List obj = lyhStringRedisTemplate.getObjArrayStr(key, clazz);
        if (LyhObjUtils.isEmpty(obj)){
            obj = (List) function.get(queryObj);
            if (LyhObjUtils.isNotEmpty(obj)){
                lyhStringRedisTemplate.setObjStr(key, obj, time, unit);
            }
        }
        return obj;
    }

    public <T> T getSetByMethod(Object keyObj, Object queryObj,LyhCachetValueAbstractFunction<T> function, Class<T> clazz, Long time, TimeUnit timeUnit){
        String key = JSON.toJSONString(keyObj);
        T obj = lyhStringRedisTemplate.getObjStr(key, clazz);
        if (LyhObjUtils.isEmpty(obj)){
            obj = function.get(queryObj);
            if (LyhObjUtils.isNotEmpty(obj)){
                lyhStringRedisTemplate.setObjStr(key, obj);
            }
        }
        return obj;
    }

}
